﻿namespace ClassyBlog.DataAccess.Specs.Unit
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Machine.Specifications;
    using NSubstitute;

    using DomainObjects;
    using ExtensionMethods;
    using Infrastructure;

    public abstract class PostRepositorySpec
    {
        protected const string DataDirectory =
            @"c:\inetpub\wwwroot\classyblog\app_data";

        protected static IPathResolver resolver;
        protected static IArticleFactory<Post> factory;
        protected static PostRepository respository;

        Establish context = () =>
        {
            resolver = Substitute.For<IPathResolver>();
            factory = Substitute.For<IArticleFactory<Post>>();

            respository = new PostRepository(DataDirectory, resolver, factory);
        };
    }

    [Subject(typeof(PageRepository))]
    public class when_creating_post_repository_with_blank_data_directory
    {
        static Exception exception;

        Because of = () => exception =
            Catch.Exception(() =>
                new PostRepository(
                    string.Empty,
                    Substitute.For<IPathResolver>(),
                    Substitute.For<IArticleFactory<Post>>()));

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentException>();
    }

    [Subject(typeof(PageRepository))]
    public class when_creating_post_repository_with_null_path_resolver
    {
        protected const string DataDirectory =
            @"c:\inetpub\wwwroot\classyblog\app_data";

        static Exception exception;

        Because of = () => exception =
            Catch.Exception(() =>
                new PostRepository(
                    DataDirectory,
                    null,
                    Substitute.For<IArticleFactory<Post>>()));

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(PageRepository))]
    public class when_creating_post_repository_with_null_factory
    {
        protected const string DataDirectory =
            @"c:\inetpub\wwwroot\classyblog\app_data";

        static Exception exception;

        Because of = () => exception =
            Catch.Exception(() =>
                new PostRepository(
                    DataDirectory, Substitute.For<IPathResolver>(), null));

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(PostRepository))]
    public class when_finding_post : PostRepositorySpec
    {
        const string FileName = "hello-world";

        static string dataPath;
        static string absoluteFilePath;

        Establish context = () =>
        {
            dataPath = DataDirectory + @"\posts";
            absoluteFilePath = dataPath + @"\" + FileName + ".md";
            resolver.Resolve(
                Arg.Any<string>(),
                Arg.Any<string>()).
                Returns(absoluteFilePath);

            factory.Create(
                Arg.Any<string>(),
                Arg.Any<string>()).
                Returns(ObjectMother.CreatePost());
        };

        Because of = () => respository.FindOne(FileName);

        It should_use_resolver_to_get_exact_file_location = () => resolver.Received().Resolve(dataPath, FileName);

        It should_use_factory_to_create_page = () => factory.Received().Create(dataPath, absoluteFilePath);
    }

    [Subject(typeof(PostRepository))]
    public class when_finding_non_existent_post : PostRepositorySpec
    {
        static Post post;

        Establish context = () =>
            resolver.Resolve(
            Arg.Any<string>(), Arg.Any<string>()).Returns((string)null);

        Because of = () => post = respository.FindOne("foo-bar");

        It should_return_null = () => post.ShouldBeNull();
    }

    [Behaviors]
    public class PostsResult
    {
        protected static IPathResolver resolver;
        protected static IArticleFactory<Post> factory;
        protected static string dataPath;
        protected static string absoluteFilePath;

        It should_use_resolver_to_get_exact_file_location = () =>
            resolver.Received().ResolveAll(dataPath, null);

        It should_use_factory_to_create_post = () =>
            factory.Received().Create(dataPath, absoluteFilePath);
    }

    public abstract class ReturnsMultiplePostsSpec : PostRepositorySpec
    {
        protected const string FileName = "2010-04-02-17-30-00_hello-world.md";

        protected static string dataPath;
        protected static string absoluteFilePath;

        protected static IEnumerable<Tag> tags;

        Establish context = () =>
        {
            dataPath = DataDirectory + @"\posts";
            absoluteFilePath = dataPath + "\\" + FileName;

            resolver.ResolveAll(
                Arg.Any<string>(),
                Arg.Any<string>()).
                Returns(new[] { absoluteFilePath });

            tags = ObjectMother.CreateTags(5);

            var posts = ObjectMother.CreatePosts(50).ToList();

            posts.Each(p => tags.Each(t => p.Tags.Add(t)));

            factory.Create(
                Arg.Any<string>(),
                Arg.Any<string>()).
                Returns(posts.First(), posts.TakeWhile((p, i) => i > 0).ToArray());
        };
    }

    [Subject(typeof(PostRepository))]
    public class when_reteriving_all_posts : ReturnsMultiplePostsSpec
    {
        Because of = () => respository.All().ToList();

        Behaves_like<PostsResult> posts;
    }

    [Subject(typeof(PostRepository))]
    public class when_counting_all_posts : ReturnsMultiplePostsSpec
    {
        Because of = () => respository.Count();

        Behaves_like<PostsResult> posts;
    }

    [Subject(typeof(PostRepository))]
    public class when_finding_posts_between_dates : ReturnsMultiplePostsSpec
    {
        Because of = () =>
            respository.FindAll(Clock.MinimumDate, Clock.MaximumDate).ToList();

        Behaves_like<PostsResult> posts;
    }

    [Subject(typeof(PostRepository))]
    public class when_counting_posts_between_dates : ReturnsMultiplePostsSpec
    {
        Because of = () =>
            respository.Count(Clock.MinimumDate, Clock.MaximumDate);

        Behaves_like<PostsResult> posts;
    }

    [Subject(typeof(PostRepository))]
    public class when_finding_posts_by_tag : ReturnsMultiplePostsSpec
    {
        Because of = () =>
            respository.FindAll(tags.ElementAt(0).Slug).ToList();

        Behaves_like<PostsResult> posts;
    }

    [Subject(typeof(PostRepository))]
    public class when_counting_posts_by_tag : ReturnsMultiplePostsSpec
    {
        Because of = () => respository.Count(tags.ElementAt(0).Slug);

        Behaves_like<PostsResult> posts;
    }

    [Subject(typeof(PostRepository))]
    public class when_counting_all_posts_of_a_null_post_repository
    {
        static PostRepository repository;
        static Exception exception;

        Establish context = () => repository = null;

        Because of = () => exception =
            Catch.Exception(() => repository.Count());

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(PostRepository))]
    public class when_finding_posts_between_dates_of_a_null_post_repository
    {
        static PostRepository repository;
        static Exception exception;

        Establish context = () => repository = null;

        Because of = () => exception =
            Catch.Exception(() => repository.FindAll(Clock.MinimumDate, Clock.MaximumDate));

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(PostRepository))]
    public class when_counting_posts_between_dates_of_a_null_post_repository
    {
        static PostRepository repository;
        static Exception exception;

        Establish context = () => repository = null;

        Because of = () => exception =
            Catch.Exception(() => repository.Count(Clock.MinimumDate, Clock.MaximumDate));

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(PostRepository))]
    public class when_finding_posts_by_tag_of_a_null_post_repository
    {
        static PostRepository repository;
        static Exception exception;

        Establish context = () => repository = null;

        Because of = () => exception =
            Catch.Exception(() => repository.FindAll("foobar"));

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(PostRepository))]
    public class when_counting_posts_by_tag_of_a_null_post_repository
    {
        static PostRepository repository;
        static Exception exception;

        Establish context = () => repository = null;

        Because of = () => exception =
            Catch.Exception(() => repository.Count("foobar"));

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentNullException>();
    }
}